#region License, Terms and Conditions
//
// Prawks - Copyright (c) 2007 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Prawks.CodeProvisioning
{
    using System;

    using MethodInfo = System.Reflection.MethodInfo;
    using StringBuilder = System.Text.StringBuilder;
    using ParameterInfo = System.Reflection.ParameterInfo;
    using Debug = System.Diagnostics.Debug;

    /// <summary>
    /// Represent a logical call to a method.
    /// </summary>
    /// <remarks>
    /// A <see cref="LogicalCall"/> instance is only as serializable as the parameters
    /// and return values it contains. This is similar to behavior of 
    /// serializable collections, which can be serialized as long as all the items they 
    /// hold are also serializable.
    /// </remarks>

    [ Serializable ]
    public sealed class LogicalCall
    {
        /// <summary>
        /// Represents an empty array of objects that can be used for parameterless calls. 
        /// This field is read-only.
        /// </summary>
        
        public static readonly object[] EmptyParameters = new object[0];

        private readonly object _target;
        private readonly MethodInfo _method;
        private readonly object[] _parameters;
        private object _returnValue;

        /// <summary>
        /// Initializes a new instance of the <see cref="LogicalCall"/> class.
        /// </summary>
        /// <param name="target">
        /// The object that is the target of the method call. This may be a null 
        /// reference for a static method call.
        /// </param>
        /// <param name="method">
        /// The method that is target of the call.
        /// </param>
        /// <param name="parameters">
        /// An array containing the actual parameter values for the call.
        /// </param>

        public LogicalCall(object target, MethodInfo method, object[] parameters)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            _target = target;
            _method = method;

            if (parameters == null || parameters.Length == 0)
            {
                _parameters = EmptyParameters;
            }
            else
            {
                _parameters = new object[parameters.Length];
                Array.Copy(parameters, _parameters, parameters.Length);
            }
        }

        /// <summary>
        /// Get the object that is the target of the call. If the <see cref="Method"/>
        /// being called is static, then this property returns a null reference.
        /// </summary>

        public object TargetObject
        {
            get { return _target; }
        }

        /// <summary>
        /// Gets the method that is the target of the call.
        /// </summary>

        public MethodInfo Method
        {
            get { return _method; }
        }

        /// <summary>
        /// Sets a value to the parameter at the specified position.
        /// </summary>
        /// <param name="index">
        /// The position of the parameter to set. 
        /// </param>
        /// <param name="value">
        /// The new value for the specified parameter.
        /// </param>
        
        public void SetParameterValue(int index, object value)
        {
            if (index < 0 || index >= _parameters.Length)
                throw new ArgumentOutOfRangeException("index");

            _parameters[index] = value;
        }

        /// <summary>
        /// Get the value of the parameter at the specified position.
        /// </summary>
        /// <param name="index">
        /// The position of the parameter to get. 
        /// </param>
        /// <returns>
        /// The value of the parameter at the specified position.
        /// </returns>
        
        public object GetParameterValue(int index)
        {
            if (index < 0 || index >= _parameters.Length)
                throw new ArgumentOutOfRangeException("index");

            return _parameters[index];
        }

        /// <summary>
        /// Gets or sets the return value from the <see cref="Method"/>. This property
        /// returns null if <see cref="LogicalCall"/> was constructed without a
        /// method reference.
        /// </summary>

        public object ReturnValue
        {
            get { return _returnValue; }
            set { _returnValue = value; }
        }

        /// <summary>
        /// Returns a <see cref="String"/> that represents the current 
        /// <see cref="LogicalCall"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="String"/> that represents the current <see cref="LogicalCall"/>.
        /// </returns>

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            builder.Append(_method.ReturnType.FullName);
            builder.Append(' ');
            builder.Append(_method.Name);
            builder.Append('(');

            bool isFirstParameter = true;

            if (_target != null)
            {
                BuildParameterString(builder, "this",
                    _target.GetType(), _target.GetHashCode());

                isFirstParameter = false;
            }

            ParameterInfo[] parameterInfos = _method.GetParameters();
            Debug.Assert(parameterInfos.Length == _parameters.Length);

            foreach (ParameterInfo parameter in _method.GetParameters())
            {
                if (!isFirstParameter)
                    builder.Append(", ");

                BuildParameterString(builder, parameter.Name,
                    parameter.ParameterType, _parameters[parameter.Position]);
            }

            builder.Append(')');

            return builder.ToString();
        }

        private static void BuildParameterString(StringBuilder builder,
            string name, Type type, object value)
        {
            builder.Append(name);
            builder.Append(" : ");
            builder.Append(type.FullName);
            builder.Append(" = {");

            if (value == null)
            {
                builder.Append("null");
            }
            else
            {
                bool isString = value is string;

                if (isString)
                    builder.Append('"');
                    
                builder.Append(value);
                    
                if (isString)
                    builder.Append('"');
            }

            builder.Append('}');
        }
    }
}
